/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.zhiqim.git.Git;
import org.zhiqim.git.GitWalker;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.objer.objectid.RevCommit;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.objer.objectid.RevTag;
import org.zhiqim.git.util.GitStreams;
import org.zhiqim.kernel.util.Asserts;

/**
 * 实现从服务端推送包到客户端处理：<br>
 * 1、多个want（对应引用表），其中首个want后面带参数<br>
 * 2、多个have（对应客户端已有的对象）<br><br>
 * 
 * clone格式如下：<br>
 * want 88bb34f4af9f686c01e3a6a09dc06a1a538c7aac multi_ack_detailed side-band-64k thin-pack ofs-delta<br>
 * want 88bb34f4af9f686c01e3a6a09dc06a1a538c7aac<br>
 * want f717c4825b48b4f784e3eafb2fdfb852a6112aa3<br>
 * done<br><br>
 * 
 * fetch格式如下：<br>
 * want c1a22ca1e7278eccd8f278c403aab4639e3e0e7b multi_ack_detailed no-done side-band-64k thin-pack ofs-delta agent=git/2.6.0.windows.1<br>
 * have f14d1ab9294ffc098918a2ca4874810f8a85b8d3<br>
 * have 85ad3d741ae2b25d4befb4453227e6075d005a94<br>
 * have 746874e27761e660fac3ad5c7d6278b0e21c166a<br>
 * have a049b79afcfaca7088802633a09a720efb6b5391<br>
 * have 211ebca8d7dede7c7bc7104fa110c9bdd6dc12e9<br>
 * have 4f9339bc408ab42367ef00470fcd0de0e4bf2e74<br>
 * have e5f523a6f61781c7c6855b667db562fb251a4398<br>
 * done<br>
 * 
 * @version v1.0.0 @author zouzhigang 2017-4-19 新建与整理
 */
public class UploadHandler implements HttpHandler
{
    private final Git git;
    private final GitWalker walker;

    private InputStream input;
    private OutputStream output;
    private UploadRequest request;

    //客户端已有列表 & 要求的引用对象列表(标签) & 要求的提交列表
    private final Set<RevCommit> haveCommitList = new HashSet<>();
    private final LinkedList<RevCommit> wantCommitList = new LinkedList<>();
    private final List<RevObject> wantRefobjList = new ArrayList<>();

    /** 构建上传包到对象 */
    public UploadHandler(Git git)
    {
        this.git = git;
        this.walker = new GitWalker(git);
        this.walker.setRetainBody(false);
    }
    
    public String getContentType()
    {
        return UPLOAD_PACK_RESULT_TYPE;
    }
    
    public boolean hasSideBand()
    {
        return request == null?false:request.hasSideBand();
    }
    
    /******************************************************************************/
    // 获取参数
    /******************************************************************************/
    
    public boolean isMultAck()
    {
        return request.isMultAck();
    }
    
    public boolean isMultAckDetailed()
    {
        return request.isMultAckDetailed();
    }
    
    public boolean hasMultAck()
    {
        return request.hasMultAck();
    }
    
    /******************************************************************************/
    // 数据处理
    /******************************************************************************/

    /**
     * 处理上传任务
     * 
     * @param input         输入流
     * @param output        输出流
     * @throws IOException  处理异常
     */
    public void execute(InputStream input, OutputStream output) throws IOException
    {
        this.input = input;
        this.output = output;

        //1.解析请求，得到want&options&have&done
        this.request = new UploadRequest().parse(this.input);
        if (!request.hasWant())
            return;
        
        //2.判断want是不是在引用表中，不是报错退出，是则增加到want列表中
        List<ObjectId> refOidList = git.getRefObjectIdList();
        for (ObjectId want : request.getWantList())
        {
            Asserts.asserts(refOidList.contains(want), "上传包错误，不支持的引用[" + want.getName() + "]");
            addWant(walker.parse(want));
        }
        
        //3.解析have，找到最新的一个have，并对所有have作标识，两边都有的回ACK
        ObjectId last = null;
        for (ObjectId oid : request.getHaveList())
        {//clone时没有have则该步跳过
            if (!addHave(oid))
            {//have不在服务端，不返回ACK
                continue;
            }
            
            //客户端和服务端匹配成功
            if (request.isMultAckDetailed())
                GitStreams.writeString(output, "ACK " + oid.getName() + " common\n");
            else if (request.isMultAck())
                GitStreams.writeString(output, "ACK " + oid.getName() + " continue\n");
            else if (last == null)
                GitStreams.writeString(output, "ACK " + oid.getName() + "\n");
            
            //保留最后一个有效have
            last = oid;
        }
        
        //4.判断是否有done标志
        if (!request.hasDoneEnd())
        {//没有done结尾
            if (haveCommitList.isEmpty() || request.hasMultAck())
                GitStreams.writeString(output, "NAK\n");
        }
        else
        {//有done结尾
            if (haveCommitList.isEmpty())
                GitStreams.writeString(output, "NAK\n");
            else if (request.hasMultAck())
                GitStreams.writeString(output, "ACK " + last.getName() + "\n");
            
            sendPack();
        }
    }
    
    /******************************************************************************/
    // addWant & addHave & sendPack
    /******************************************************************************/
    
    private void addWant(RevObject obj) throws IOException
    {
        if (obj.isWant())
            return;
        
        obj.setWant();
        if (obj instanceof RevCommit)
            wantCommitList.add((RevCommit)obj);
        else
            wantRefobjList.add(obj);
        
        if (obj instanceof RevTag)
        {//如果是标签，找到对应的提交
            obj = walker.peel(obj);
            if (obj instanceof RevCommit)
                addWant(obj);
        }
    }

    private boolean addHave(ObjectId id) throws IOException
    {
        RevCommit commit = walker.parseCommitNullable(id);
        if (commit == null)
        {//对象不在服务端，返回false表示不需要回ACK
            return false;
        }
        
        if (commit.isHave())
        {//对象存在，且已标识在父提交中，返回true表示要回ACK
            return true;
        }
        
        //递归设置have标志到所有父提交中
        commit.setHave();
        commit.setHaveParentRecursion(walker, haveCommitList);
        haveCommitList.add(commit);
        
        //对象存在服务端，返回true表示要回ACK
        return true;
    }

    private void sendPack() throws IOException
    {
        boolean isPprogress = !request.isNoProgress();
        boolean hasSideband = request.hasSideBand();

        Progress progress = ProgressEmpty.getInstance();
        OutputStream packOut = output;

        if (hasSideband)
        {
            int bufsz = request.isSideBand64k()?SB_MAX_BUF:SB_MIN_BUF;
            packOut = new SideBandOutputStream(SB_CH_DATA, bufsz, output);
            if (isPprogress)
                progress = new ProgressSideBand(new SideBandOutputStream(SB_CH_PROGRESS, bufsz, output));
        }
        
        UploadWriter pw = new UploadWriter(git, walker, progress);
        
        //预处理包，
        //wantRefobjList为要求的引用对象列表(需要发送自己)，
        //wantCommitList为需要的提交列表（需要发送tree&blob）
        pw.preparePack(wantRefobjList, wantCommitList, haveCommitList);
        
        pw.writePack(packOut);
        packOut.flush();

        if (hasSideband)
            GitStreams.writeEnd(output);
    }
}
